import os
import asyncio
from playwright.async_api import async_playwright
import time
import json
import datetime


from typing import Dict
# 导入日志系统
from logger import get_logger

# 获取日志实例
logger = get_logger("DouyinMultiAccountBot")


class DouyinMultiAccountBot:
    """多账号抖音自动回复机器人"""

    def __init__(self, config_path: str = 'accounts_config.json'):
        self.config_path = config_path
        self.accounts = {}
        self.playwright = None
        self.tasks = []

    async def load_accounts(self):
        """从配置文件加载账号信息，包含Cookie文件路径"""
        if not os.path.exists(self.config_path):
            logger.error(f"账号配置文件不存在: {self.config_path}")
            return False

        try:
            with open(self.config_path, 'r', encoding='utf-8') as f:
                accounts_config = json.load(f)

            for account_id, config in accounts_config.items():
                # 确保Cookie路径有效
                if 'cookies_path' not in config:
                    config['cookies_path'] = os.path.join('accounts', account_id, 'cookies.json')
                self.accounts[account_id] = Account(account_id, config)
                logger.info(f"已加载账号配置: {account_id}，Cookie路径: {config['cookies_path']}")

            return True
        except Exception as e:
            logger.error(f"加载账号配置文件失败: {e}")
            logger.log_traceback()
            return False

    async def start(self):
        """启动多账号监控"""
        logger.info("开始启动多账号监控系统...")

        # 初始化Playwright
        self.playwright = await async_playwright().start()
        logger.info("Playwright已初始化")

        # 加载账号配置
        if not await self.load_accounts():
            logger.error("账号配置加载失败，退出程序")
            return

        # 为每个账号创建运行任务
        for account_id, account in self.accounts.items():
            task = asyncio.create_task(account.run(self.playwright))
            self.tasks.append(task)
            logger.info(f"账号 {account_id} 监控任务已启动")

        # 等待所有任务完成
        try:
            await asyncio.gather(*self.tasks)
        except Exception as e:
            logger.critical(f"多账号监控系统发生异常: {e}")
            logger.log_traceback()
        finally:
            await self.stop()

    async def stop(self):
        """停止所有账号监控"""
        logger.info("正在停止多账号监控系统...")

        # 取消所有任务
        for task in self.tasks:
            if not task.done():
                task.cancel()

        # 关闭Playwright
        if self.playwright:
            await self.playwright.stop()
            logger.info("Playwright已关闭")

        logger.info("多账号监控系统已停止")


class Account:
    """单个抖音账号的管理类"""

    def __init__(self, account_id: str, config: Dict):
        self.account_id = account_id
        self.config = config
        self.login_success = False
        self.browser = None
        self.context = None
        self.page = None
        self.reply_limit_per_user = config.get('reply_limit', 1)
        self.reply_text = config.get('reply_text', "需要联系加v")
        self.headless = config.get('headless', False)
        self.cookies_path = config.get('cookies_path', os.path.join('accounts', account_id, 'cookies.json'))
        self.user_counter_path = os.path.join('accounts', account_id, 'user_counter.json')
        self.processed_conversations = set()
        self.user_counter = {}  # 内存中的用户计数器
        self.target_offset = 0
        self.user_container_height = 65
        self.monitor_user_count = 20
        self.scroll_container_selector = 'div.iXcuFCYr'  # 滚动容器选择器
        self.user_container_selector = 'div.qYJqdyUV'  # 用户容器选择器
        self.triggered_self_reply = {}

        self.interrupt_event = asyncio.Event()  # 中断信号（高优任务触发）
        self.currently_processing = False

        # 确保账号目录存在
        account_dir = os.path.join('accounts', account_id)
        if not os.path.exists(account_dir):
            os.makedirs(account_dir)

    async def login(self, playwright):
        """登录抖音，从指定路径加载Cookie"""
        try:
            # 使用launch_persistent_context方法代替launch
            user_data_dir = os.path.join('accounts', self.account_id, 'user_data')
            self.context = await playwright.chromium.launch_persistent_context(
                user_data_dir,
                headless=self.headless,
                accept_downloads=True,
                bypass_csp=True,
                args=[
                    '--disable-blink-features=AutomationControlled',
                    '--disable-infobars',
                    '--disable-automation',
                    '--no-sandbox',
                    '--disable-setuid-sandbox',
                ]
            )

            # 不再需要单独创建browser对象，context已包含浏览器实例
            self.page = self.context.pages[0] if self.context.pages else await self.context.new_page()

            # 从指定路径加载Cookie
            cookies = await self.load_cookies()
            if cookies:
                await self.context.add_cookies(cookies)
                logger.info(f"账号 {self.account_id} 已从文件加载Cookie")
            else:
                logger.warning(f"账号 {self.account_id} 未加载到有效Cookie，需要手动登录")

            await self.page.add_init_script("""
                            Object.defineProperty(navigator, 'webdriver', {
                                get: () => false
                            });
                            window.navigator.chrome = {
                                runtime: {},
                                csi: function() { return {}; },
                                loadTimes: function() { return { requestTime: (new Date()).getTime() }; }
                            };
                            window.chrome = {
                                runtime: {},
                                csi: function() { return {}; },
                                loadTimes: function() { return { requestTime: (new Date()).getTime() }; }
                            };
                            window.navigator.languages = ['zh-CN', 'zh'];
                            window.navigator.permissions = {
                                query: parameters => new Promise(resolve => resolve({ state: 'granted' }))
                            };
                        """)

            await self.page.goto("https://www.douyin.com")

            start_time = time.time()
            timeout = 8 * 60  # 8 分钟

            login_prompt_locator = self.page.locator("div > div > div.QgsIceRc > div")
            await asyncio.sleep(2)
            while time.time() - start_time < timeout:
                if await login_prompt_locator.is_visible():
                    logger.info(f"账号 {self.account_id} 当前尚未登录，需要手动登录...")
                    logger.info(f"账号 {self.account_id} 请在打开的浏览器中完成登录")
                    await asyncio.sleep(5)
                else:
                    logger.info(f"账号 {self.account_id} 已登录")
                    self.login_success = True
                    await self.save_cookies()  # 保存登录后的Cookie
                    break

        except Exception as e:
            logger.critical(f"账号 {self.account_id} 登录过程中发生严重错误: {e}")
            logger.log_traceback()
            raise

    async def load_cookies(self):
        """从指定路径加载cookies"""
        if os.path.exists(self.cookies_path):
            try:
                with open(self.cookies_path, 'r', encoding='utf-8') as f:
                    cookies = json.load(f)
                    # 修复Cookie格式
                    for cookie in cookies:
                        if cookie.get('domain') == 'www.douyin.com':
                            cookie['domain'] = '.douyin.com'
                        if isinstance(cookie.get('expires'), str):
                            try:
                                dt = datetime.datetime.fromisoformat(cookie['expires'].replace("Z", "+00:00"))
                                cookie['expires'] = int(dt.timestamp())
                            except Exception:
                                cookie['expires'] = None
                        cookie.setdefault('httpOnly', False)
                        cookie.setdefault('secure', True)
                        if cookie.get('sameSite') not in ['Strict', 'Lax', 'None']:
                            cookie['sameSite'] = 'None'
                    logger.debug(f"账号 {self.account_id} 已从文件加载{len(cookies)}个cookies")
                    return cookies
            except Exception as e:
                logger.error(f"账号 {self.account_id} 加载Cookie文件失败: {e}")
        logger.warning(f"账号 {self.account_id} Cookie文件不存在或格式错误: {self.cookies_path}")
        return []

    async def load_user_counter(self):
        """从文件加载用户回复计数器"""
        if os.path.exists(self.user_counter_path):
            with open(self.user_counter_path, 'r', encoding='utf-8') as f:
                self.user_counter = json.load(f)
            logger.info(f"账号 {self.account_id} 已加载用户计数器")
        else:
            self.user_counter = {}
            logger.info(f"账号 {self.account_id} 用户计数器文件不存在，创建新的计数器")

    async def save_user_counter(self):
        """将用户计数器保存到文件"""
        with open(self.user_counter_path, 'w', encoding='utf-8') as f:
            json.dump(self.user_counter, f, indent=2, ensure_ascii=False)
        logger.info(f"账号 {self.account_id} 用户计数器已保存")

    async def save_cookies(self):
        """保存当前上下文中的 cookies 到文件"""
        if not self.context:
            return

        cookies = await self.context.cookies()
        with open(self.cookies_path, 'w', encoding='utf-8') as f:
            json.dump(cookies, f, indent=2, ensure_ascii=False)
        logger.info(f"账号 {self.account_id} 登录态已保存到 {self.cookies_path}")

    async def get_current_username(self):
        """获取当前会话的用户名"""
        try:
            username_element = self.page.locator('//div[@class="ZJm3Obdh sQEpa_tx"]')
            if await username_element.is_visible():
                return await username_element.inner_text()
        except Exception as e:
            logger.error(f"账号 {self.account_id} 获取用户名失败：{e}")
        return "unknown_user"

    async def check_and_update_user_counter(self, username: str):
        """检查并更新用户计数器，每日重置"""
        now = datetime.datetime.now()

        user_data = self.user_counter.get(username, {"count": 0, "last_time": None})
        count = user_data["count"]
        last_time_str = user_data["last_time"]

        # 如果上次记录时间超过24小时，则重置计数
        if last_time_str:
            try:
                last_time = datetime.datetime.fromisoformat(str(last_time_str))
                if (now - last_time).total_seconds() > 86400:
                    logger.info(f"账号 {self.account_id} 用户 {username} 计数已过期，重置计数")
                    count = 0
            except ValueError:
                logger.warning(f"账号 {self.account_id} 用户 {username} 的时间格式错误，已重置")
                count = 0

        if count >= self.reply_limit_per_user:
            logger.info(f"账号 {self.account_id} 用户 {username} 超过最大回复次数，跳过")
            return False

        new_count = count + 1
        self.user_counter[username] = {
            "count": new_count,
            "last_time": now.isoformat()
        }
        await self.save_user_counter()
        logger.info(f"账号 {self.account_id} 用户 {username} 计数器更新为: {new_count}")
        return True

    async def determine_message_sender(self, container):
        try:
            # 判断是否为 "own" 消息（即自己发送的消息）
            own_message_locator = container.locator('div.p5hPCU8M.o3ZPy9DT')
            if await own_message_locator.is_visible():
                # 使用更精确的定位器提取文本内容
                text_locator = own_message_locator.locator(
                    'div.SZtuWb3S.tIJOLE11 > div > div > div > div > span > span > span > span > pre'
                )
                if await text_locator.is_visible():
                    message_text = await text_locator.inner_text()
                    self.triggered_self_reply["last"] = "你好" in message_text
                    return "own", "你好" in message_text
                else:
                    logger.warning("[determine_message_sender] own 消息中的文本容器不可见")
                    return "own", False

            # 判断是否为 "opponent" 消息（对方发送的消息）
            opponent_message_locator = container.locator('div.o3ZPy9DT')
            if await opponent_message_locator.is_visible():
                return "opponent", True

            # 默认情况：无法识别的消息来源
            logger.warning(f"[determine_message_sender] 匹配失败，container: {await container.inner_html()}")
            return "unknown", False

        except Exception as e:
            logger.error(f"判断消息发送者失败: {e}")
            return "unknown", False

    async def reply_in_current_conversation(self):
        try:
            await self.page.wait_for_selector('div.A1KpIsbL', timeout=5000)

            first_container = self.page.locator('div.A1KpIsbL.HO4aqgd4').first

            if not await first_container.is_visible():
                logger.info("首个消息容器不可见，跳过")
                return

            sender, should_reply = await self.determine_message_sender(first_container)
            logger.info(f"首个消息容器: 发送者={sender}, 需要回复={should_reply}")

            if should_reply:
                username = await self.get_current_username()
                can_reply = await self.check_and_update_user_counter(username)

                if can_reply:
                    await self._send_reply()
                    logger.info("已对首个消息容器进行回复")
                else:
                    logger.info(f"账号 {self.account_id} 用户 {username} 已达到回复上限，跳过回复")
            else:
                logger.info("首个消息无需回复")
        except Exception as e:
            logger.error(f"处理首个消息容器时发生错误: {e}")

    async def _send_reply(self):
        """执行具体的回复操作"""
        input_box = self.page.locator('div.DraftEditor-editorContainer >> div >> div >> div >> div')
        send_btn = self.page.locator('span.sCp7KhBv.e2e-send-msg-btn > svg')
        reply_text = self.reply_text  # 预设回复内容

        try:
            await input_box.click()
            await input_box.fill(reply_text)
            if await send_btn.is_visible():
                await send_btn.click()
                logger.info("回复发送成功")
            else:
                logger.error("发送按钮未加载，跳过")
        except Exception as e:
            logger.error(f"回复失败：{e}")

    async def check_new_conversation(self):
        """
        检测是否有包含小红点或关键词的新会话
        :return: Tuple[bool, List[Locator]] 是否检测到新会话，以及匹配的会话元素列表
        """
        try:
            # 定位会话容器
            conversation_list = self.page.locator('//div[@class="iXcuFCYr"]')
            if not await conversation_list.is_visible():
                conversation_items = self.page.locator('//div[@class="iXcuFCYr"]/div')
            else:
                conversation_items = conversation_list.locator('//div[@class="qYJqdyUV"]')

            count = await conversation_items.count()
            logger.debug(f"共找到 {count} 个会话项")
            matched_conversations = []

            for i in range(count):
                item = conversation_items.nth(i)

                # 等待可见性
                if not await item.is_visible():
                    continue

                # 检查是否有小红点
                has_notification = False
                for selector in ['div.hcPUqxqn.noSemiGlobal > span > span',
                                 '//div[@class="hcPUqxqn noSemiGlobal"]/span']:
                    notification_indicator = item.locator(selector)
                    if await notification_indicator.is_visible():
                        has_notification = True
                        break

                # 检查是否包含关键词 "你好"
                has_keyword = False
                for selector in ['div.wiYIhq4q > div.DDOhSZqR.C0_yyLLi > pre']:
                    text_element = item.locator(selector)
                    if await text_element.is_visible():
                        full_text = await text_element.inner_text()
                        if "你好" in full_text.strip().lower():
                            has_keyword = True
                            break

                # 判断是否匹配
                if has_notification or has_keyword:
                    logger.info(f"会话{i + 1}符合条件（触发源: {'小红点' if has_notification else '关键词'}）")
                    matched_conversations.append(item)

            if matched_conversations:
                logger.info(f"检测到 {len(matched_conversations)} 条新会话")
                return True, matched_conversations
            else:
                logger.info("未检测到新会话")
                return False, []

        except Exception as e:
            logger.error(f"检测新会话失败: {e}")
            return False, []

    async def daily_reset_task(self):
        """每天凌晨自动清空用户计数器"""
        while True:
            try:
                now = datetime.datetime.now()
                tomorrow = now + datetime.timedelta(days=1)
                next_run = datetime.datetime.combine(tomorrow.date(), datetime.time.min)
                seconds_until_midnight = (next_run - now).total_seconds()

                logger.info(f"账号 {self.account_id} 下次自动清空计数器时间：{next_run}")
                await asyncio.sleep(seconds_until_midnight)

                logger.info(f"账号 {self.account_id} 正在执行每日计数器重置...")
                self.processed_conversations.clear()
                await self.save_user_counter()
                logger.info(f"账号 {self.account_id} 用户计数器已重置")
            except Exception as e:
                logger.error(f"账号 {self.account_id} 每日重置任务发生错误: {e}")
                logger.log_traceback()
                await asyncio.sleep(3600)

    async def check_new_conversations_loop(self):
        """【高优先级】主循环：检查是否有新会话，并持续监听当前聊天窗口"""
        logger.info(f"账号 {self.account_id} 启动高优先级监听器")

        while True:
            try:
                success, new_conversations = await self.check_new_conversation()

                if success and new_conversations:
                    for conversation in new_conversations:
                        # 触发中断
                        self.interrupt_event.set()
                        logger.info("高优任务触发中断，暂停次优任务")

                        # 点击进入会话
                        await conversation.click()
                        await asyncio.sleep(2)

                        # 开始监听当前聊天窗口
                        reply_count = 0
                        while True:
                            try:
                                message_container = self.page.locator('div.A1KpIsbL.HO4aqgd4').first
                                if await message_container.is_visible():
                                    await self.reply_in_current_conversation()
                                    reply_count += 1

                                recheck = await self.check_new_conversation()  # 再次检查是否有新会话
                                if recheck:
                                    logger.info("检测到更高优先级的新会话，终止当前监听")
                                    break

                                await asyncio.sleep(1)
                            except Exception as e:
                                logger.error(f"[高优聊天监听] 异常: {e}")
                                break

                        self.interrupt_event.clear()
                        logger.info("高优任务完成，恢复次优任务")

                await asyncio.sleep(3)
            except Exception as e:
                logger.error(f"[高优会话监听] 异常: {e}")
                await asyncio.sleep(5)

    async def monitor_current_conversation(self):
        """【次优先级】主循环：监控当前会话，收到中断信号时暂停"""
        logger.info(f"账号 {self.account_id} 启动当前会话监听器（次优先级）")

        while True:
            try:
                # 检查中断信号：若触发则暂停
                if self.interrupt_event.is_set():
                    logger.info("收到中断信号，次优任务暂停")
                    await self.interrupt_event.wait()

                # 若高优先级任务正在处理，等待
                if self.currently_processing:
                    await asyncio.sleep(1)
                    continue

                # 正常监控当前会话
                message_container = self.page.locator('div.A1KpIsbL.HO4aqgd4').first
                if await message_container.is_visible():
                    await self.reply_in_current_conversation()

                await asyncio.sleep(1)  # 每秒检查一次
            except Exception as e:
                logger.error(f"[当前会话监听] 异常: {e}")
                await asyncio.sleep(3)

    async def ensure_task_runs_forever(self, task_coro, task_name):
        while True:
            try:
                await task_coro()
            except asyncio.CancelledError:
                logger.info(f"{task_name} 被取消")
                break
            except Exception as e:
                logger.critical(f"{task_name} 出现致命错误: {e}")
                await asyncio.sleep(5)
    async def start_listening(self):
        if not self.login_success:
            logger.error(f"账号 {self.account_id} 未登录，无法开始监听")
            return

        logger.info(f"账号 {self.account_id} 开始监听私信消息...")

        reset_task = asyncio.create_task(self.daily_reset_task())
        new_conv_task = asyncio.create_task(
            self.ensure_task_runs_forever(self.check_new_conversations_loop, "高优监听"))
        curr_conv_task = asyncio.create_task(
            self.ensure_task_runs_forever(self.monitor_current_conversation, "次优监听"))

        try:
            await asyncio.gather(new_conv_task, curr_conv_task)
        except asyncio.CancelledError:
            logger.info(f"账号 {self.account_id} 的监听任务被取消")
        finally:
            reset_task.cancel()
            new_conv_task.cancel()
            curr_conv_task.cancel()

    async def navigate_to_messages(self):
        """导航到私信页面并开始监控"""
        if not self.login_success:
            logger.error(f"账号 {self.account_id} 未登录，无法导航到私信")
            return False

        try:
            # 导航到私信页面
            message_button = self.page.get_by_text("私信").first
            await self.page.wait_for_selector("text=私信", state="visible", timeout=20000)
            await message_button.hover()
            await message_button.click()
            logger.info(f"账号 {self.account_id} 已进入私信页面")
            await asyncio.sleep(3)

            # 如果没有新会话，进入第一个会话并开始监控
            await self.page.wait_for_selector('ul.iqAeEhI1 li.hA2I3Wjd', state='visible', timeout=10000)
            first_user_session = self.page.locator('div.iXcuFCYr div.qYJqdyUV').first

            if await first_user_session.is_visible():
                await first_user_session.click()
                logger.info(f"账号 {self.account_id} 已进入第一个会话")
                await asyncio.sleep(2)

                # 点击指定元素开始监控
                target_element = self.page.locator('div > div.eNnOZTpe > span > svg')
                if await target_element.is_visible():
                    await target_element.click()
                    logger.info(f"账号 {self.account_id} 已点击指定元素，准备开始监控")
                    await asyncio.sleep(2)

                    # 立即尝试回复一次
                    logger.info("【触发源: 初始点击】首次进入聊天窗口，尝试回复")
                    await self.reply_in_current_conversation()

                    return True
                else:
                    logger.error(f"指定元素不可见，可能页面结构已更新")
                    return False
            else:
                logger.error(f"第一个用户会话不可见")
                return False
        except Exception as e:
            logger.error(f"导航到私信页面时发生错误: {e}")
            return False


    async def run(self, playwright):
        """运行账号监控任务"""
        try:
            logger.info(f"开始运行账号 {self.account_id} 监控任务...")
            await self.load_user_counter()
            await self.login(playwright)

            if self.login_success:
                # 进入私信页面并开始监控
                if await self.navigate_to_messages():
                    # 开始监听消息
                    await self.start_listening()
                else:
                    logger.error(f"账号 {self.account_id} 进入私信页面失败")

        except Exception as e:
            logger.critical(f"账号 {self.account_id} 运行异常：{e}")
            logger.log_traceback()
        finally:
            if self.browser:
                await self.browser.close()
                logger.info(f"账号 {self.account_id} 浏览器已关闭")


# 主程序入口
if __name__ == "__main__":
    logger.info("多账号抖音自动回复机器人启动")
    bot = DouyinMultiAccountBot()
    asyncio.run(bot.start())
